"""
SSRF Validator Tests

Tests for the SSRF (Server-Side Request Forgery) protection that validates URLs
before making outgoing HTTP requests.

Security model:
- By default, block all private/internal IPs (RFC1918, localhost, link-local)
- allow_localhost=True: Allow only loopback addresses (127.x.x.x, ::1)
- allow_private_ips=True: Allow all RFC1918 private IPs + localhost
- AWS metadata endpoint (169.254.169.254) is ALWAYS blocked

The allow_private_ips parameter is designed for trusted self-hosted services like
SearXNG that may be running on a different machine on the local network.
"""

import pytest
from unittest.mock import patch

from tests.test_utils import add_src_to_path

add_src_to_path()


@pytest.fixture(autouse=True)
def disable_ssrf_test_bypass():
    """
    Disable the test mode bypass for SSRF validation.

    The SSRF validator bypasses validation when PYTEST_CURRENT_TEST is set.
    We need to mock the os.environ.get calls to test the actual validation.
    """
    with (
        patch(
            "src.local_deep_research.security.ssrf_validator.os.environ.get"
        ) as mock_env_get,
        patch(
            "src.local_deep_research.security.ssrf_validator.get_env_setting"
        ) as mock_get_env_setting,
    ):
        # Return empty string for test bypass checks (so .lower() works)
        # Return None for PYTEST_CURRENT_TEST (it's checked for truthiness)
        def mock_environ_get(key, default=""):
            if key == "TESTING":
                return ""  # Empty string so .lower() works
            if key == "PYTEST_CURRENT_TEST":
                return None  # None is falsy, bypasses the check
            return default

        mock_env_get.side_effect = mock_environ_get
        # Return False for disable_validation setting
        mock_get_env_setting.return_value = False
        yield


class TestIsIpBlocked:
    """Test the is_ip_blocked function."""

    def test_localhost_blocked_by_default(self):
        """Localhost should be blocked by default."""
        from src.local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        assert is_ip_blocked("127.0.0.1") is True
        assert is_ip_blocked("127.0.0.2") is True

    def test_localhost_allowed_with_allow_localhost(self):
        """Localhost should be allowed with allow_localhost=True."""
        from src.local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        assert is_ip_blocked("127.0.0.1", allow_localhost=True) is False
        assert is_ip_blocked("127.0.0.2", allow_localhost=True) is False

    def test_private_ip_blocked_with_allow_localhost(self):
        """Private IPs should still be blocked with allow_localhost=True."""
        from src.local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        assert is_ip_blocked("192.168.1.100", allow_localhost=True) is True
        assert is_ip_blocked("10.0.0.5", allow_localhost=True) is True
        assert is_ip_blocked("172.16.0.1", allow_localhost=True) is True

    def test_private_ip_allowed_with_allow_private_ips(self):
        """Private IPs should be allowed with allow_private_ips=True."""
        from src.local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        # 192.168.x.x range
        assert is_ip_blocked("192.168.1.100", allow_private_ips=True) is False
        assert is_ip_blocked("192.168.0.1", allow_private_ips=True) is False
        assert is_ip_blocked("192.168.255.255", allow_private_ips=True) is False

        # 10.x.x.x range
        assert is_ip_blocked("10.0.0.1", allow_private_ips=True) is False
        assert is_ip_blocked("10.255.255.255", allow_private_ips=True) is False

        # 172.16-31.x.x range
        assert is_ip_blocked("172.16.0.1", allow_private_ips=True) is False
        assert is_ip_blocked("172.31.255.255", allow_private_ips=True) is False

    def test_localhost_also_allowed_with_allow_private_ips(self):
        """Localhost should also be allowed with allow_private_ips=True."""
        from src.local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        assert is_ip_blocked("127.0.0.1", allow_private_ips=True) is False
        assert is_ip_blocked("127.0.0.2", allow_private_ips=True) is False

    def test_aws_metadata_always_blocked(self):
        """AWS metadata endpoint should ALWAYS be blocked, even with allow_private_ips."""
        from src.local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        # Without any allowlist
        assert is_ip_blocked("169.254.169.254") is True

        # With allow_localhost
        assert is_ip_blocked("169.254.169.254", allow_localhost=True) is True

        # With allow_private_ips - CRITICAL: Must still be blocked!
        assert is_ip_blocked("169.254.169.254", allow_private_ips=True) is True

    def test_public_ip_not_blocked(self):
        """Public IPs should not be blocked."""
        from src.local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        assert is_ip_blocked("8.8.8.8") is False
        assert is_ip_blocked("1.1.1.1") is False
        assert is_ip_blocked("142.250.185.206") is False  # google.com

    def test_link_local_blocked(self):
        """Link-local addresses should be blocked."""
        from src.local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        assert is_ip_blocked("169.254.1.1") is True
        assert is_ip_blocked("169.254.100.100") is True


class TestValidateUrl:
    """Test the validate_url function."""

    def test_public_url_allowed(self):
        """Public URLs should be allowed."""
        from src.local_deep_research.security.ssrf_validator import validate_url

        with patch("socket.getaddrinfo") as mock_getaddrinfo:
            # Mock DNS resolution to return a public IP
            mock_getaddrinfo.return_value = [
                (2, 1, 6, "", ("142.250.185.206", 0))
            ]
            assert validate_url("https://google.com") is True

    def test_localhost_url_blocked_by_default(self):
        """Localhost URLs should be blocked by default."""
        from src.local_deep_research.security.ssrf_validator import validate_url

        assert validate_url("http://127.0.0.1:8080") is False
        assert validate_url("http://localhost:8080") is False

    def test_localhost_url_allowed_with_allow_localhost(self):
        """Localhost URLs should be allowed with allow_localhost=True."""
        from src.local_deep_research.security.ssrf_validator import validate_url

        with patch("socket.getaddrinfo") as mock_getaddrinfo:
            mock_getaddrinfo.return_value = [(2, 1, 6, "", ("127.0.0.1", 0))]
            assert (
                validate_url("http://localhost:8080", allow_localhost=True)
                is True
            )

        assert (
            validate_url("http://127.0.0.1:8080", allow_localhost=True) is True
        )

    def test_private_ip_url_blocked_with_allow_localhost(self):
        """Private IP URLs should still be blocked with allow_localhost=True."""
        from src.local_deep_research.security.ssrf_validator import validate_url

        assert (
            validate_url("http://192.168.1.100:8080", allow_localhost=True)
            is False
        )
        assert (
            validate_url("http://10.0.0.5:8080", allow_localhost=True) is False
        )

    def test_private_ip_url_allowed_with_allow_private_ips(self):
        """Private IP URLs should be allowed with allow_private_ips=True."""
        from src.local_deep_research.security.ssrf_validator import validate_url

        # 192.168.x.x - typical home network
        assert (
            validate_url("http://192.168.1.100:8080", allow_private_ips=True)
            is True
        )
        assert (
            validate_url("http://192.168.0.1:80", allow_private_ips=True)
            is True
        )

        # 10.x.x.x - typical corporate network
        assert (
            validate_url("http://10.0.0.5:8080", allow_private_ips=True) is True
        )
        assert (
            validate_url("http://10.10.10.10:3000", allow_private_ips=True)
            is True
        )

        # 172.16-31.x.x - Docker default network etc.
        assert (
            validate_url("http://172.16.0.1:8080", allow_private_ips=True)
            is True
        )
        assert (
            validate_url("http://172.20.0.2:5000", allow_private_ips=True)
            is True
        )

    def test_aws_metadata_url_always_blocked(self):
        """AWS metadata URL should ALWAYS be blocked."""
        from src.local_deep_research.security.ssrf_validator import validate_url

        aws_metadata_url = "http://169.254.169.254/latest/meta-data"

        # Without any allowlist
        assert validate_url(aws_metadata_url) is False

        # With allow_localhost
        assert validate_url(aws_metadata_url, allow_localhost=True) is False

        # With allow_private_ips - CRITICAL: Must still be blocked!
        assert validate_url(aws_metadata_url, allow_private_ips=True) is False

    def test_invalid_scheme_blocked(self):
        """Invalid schemes should be blocked."""
        from src.local_deep_research.security.ssrf_validator import validate_url

        assert validate_url("ftp://example.com") is False
        assert validate_url("file:///etc/passwd") is False
        assert validate_url("javascript:alert(1)") is False

    def test_hostname_resolving_to_private_ip_blocked(self):
        """Hostnames that resolve to private IPs should be blocked."""
        from src.local_deep_research.security.ssrf_validator import validate_url

        with patch("socket.getaddrinfo") as mock_getaddrinfo:
            # Simulate a hostname resolving to a private IP (DNS rebinding attack)
            mock_getaddrinfo.return_value = [(2, 1, 6, "", ("192.168.1.1", 0))]
            assert validate_url("http://evil.com") is False

    def test_hostname_resolving_to_private_ip_allowed_with_allow_private_ips(
        self,
    ):
        """Hostnames resolving to private IPs allowed with allow_private_ips=True."""
        from src.local_deep_research.security.ssrf_validator import validate_url

        with patch("socket.getaddrinfo") as mock_getaddrinfo:
            mock_getaddrinfo.return_value = [
                (2, 1, 6, "", ("192.168.1.100", 0))
            ]
            assert (
                validate_url("http://my-searxng.local", allow_private_ips=True)
                is True
            )


class TestSearXNGUseCase:
    """Test the specific SearXNG use case that motivated allow_private_ips."""

    def test_searxng_on_localhost(self):
        """SearXNG on localhost should work with allow_private_ips."""
        from src.local_deep_research.security.ssrf_validator import validate_url

        with patch("socket.getaddrinfo") as mock_getaddrinfo:
            mock_getaddrinfo.return_value = [(2, 1, 6, "", ("127.0.0.1", 0))]
            assert (
                validate_url("http://localhost:8080", allow_private_ips=True)
                is True
            )

        assert (
            validate_url("http://127.0.0.1:8080", allow_private_ips=True)
            is True
        )

    def test_searxng_on_lan(self):
        """SearXNG on LAN should work with allow_private_ips."""
        from src.local_deep_research.security.ssrf_validator import validate_url

        # Home network
        assert (
            validate_url("http://192.168.1.100:8080", allow_private_ips=True)
            is True
        )

        # NAS or server on network
        assert (
            validate_url("http://10.0.0.50:8888", allow_private_ips=True)
            is True
        )

        # Docker network
        assert (
            validate_url("http://172.17.0.2:8080", allow_private_ips=True)
            is True
        )

    def test_searxng_hostname_on_lan(self):
        """SearXNG with hostname on LAN should work with allow_private_ips."""
        from src.local_deep_research.security.ssrf_validator import validate_url

        with patch("socket.getaddrinfo") as mock_getaddrinfo:
            # Simulate local DNS or /etc/hosts entry
            mock_getaddrinfo.return_value = [(2, 1, 6, "", ("192.168.1.50", 0))]
            assert (
                validate_url(
                    "http://searxng.local:8080", allow_private_ips=True
                )
                is True
            )


class TestDocumentation:
    """Documentation tests explaining the security model."""

    def test_security_model_documentation(self):
        """
        Document the SSRF protection security model.

        WHY THIS EXISTS:
        - SSRF attacks can be used to access internal services
        - Attackers can steal credentials from cloud metadata endpoints
        - Internal services often have weaker security (no auth required)

        THE allow_private_ips PARAMETER:
        - Designed for trusted self-hosted services like SearXNG
        - SearXNG URL is admin-configured, not arbitrary user input
        - Users intentionally run SearXNG on their local network
        - This is NOT the classic SSRF vector (user submits URL)

        CRITICAL: AWS METADATA IS ALWAYS BLOCKED:
        - 169.254.169.254 is the #1 SSRF target for credential theft
        - Even with allow_private_ips=True, this endpoint is blocked
        - This protects against credential theft in cloud environments

        SECURITY MODEL:
        | Parameter           | Localhost | Private IPs | AWS Metadata |
        |---------------------|-----------|-------------|--------------|
        | (default)           | Blocked   | Blocked     | Blocked      |
        | allow_localhost     | Allowed   | Blocked     | Blocked      |
        | allow_private_ips   | Allowed   | Allowed     | BLOCKED      |
        """
        assert True  # Documentation test
